/*
 * Eugen Stoian <stoian.e@gmail.com> 2010
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
#include "CurveSegment3D.h"
#include "MathParser.h"
#include "Expression3D.h"
#include "Parameter.h"
#include "Project.h"
#include <cfloat>
#include <GL/gl.h>
CurveSegment3D::CurveSegment3D(QObject *parent)
    :ParametricObject3D(parent),_next(0){

}

CurveSegment3D::CurveSegment3D(const Expression3D *expression, QObject *parent)
    :ParametricObject3D(expression, parent),_next(0){
}



void CurveSegment3D::compute() {
    cleanUpPoints();
    if(!isValid())
        return;
    SmartPtr<ExpressionTreeNode> xTree = _expression->xTree();
    SmartPtr<ExpressionTreeNode> yTree = _expression->yTree();
    SmartPtr<ExpressionTreeNode> zTree = _expression->zTree();

    QList<Parameter*>::const_iterator it;
    for(it = _parameters.begin();it!=_parameters.end();++it){
        xTree->bind((*it)->objectName(),(*it)->data());
        yTree->bind((*it)->objectName(),(*it)->data());
        zTree->bind((*it)->objectName(),(*it)->data());
    }
    QString marker1, marker2;

    Parameter *param = autoParameters().first();
    param->reset();
    if(param->interval().length()<DBL_EPSILON){
        return;
    }
    double step = param->delta()/100;
    /*Cauta in dreapta capatul stang al primului interval pe care curba ia valori finite*/
    Point P = Point(xTree->eval(),yTree->eval(),zTree->eval());
    double upper = param->value()+param->delta();
    while(!P.isValid() || !P.isFinite())
    {
        (*param)+=step;
        if(param->value()>=upper){
            ++(*param);
            upper = param->value()+param->delta();
        }
        if(param->isOutOfRange())
            break;
        P = Point(xTree->eval(),yTree->eval(),zTree->eval());
    }
    if(param->isOutOfRange())
    {
        /*nu s-a gasit nici un punct valid*/
        return;
    }
    /*In P se afla primul punct*/
    P.setParameterValue(param->value());
    _points[param->value()] = P;
    marker1 = xTree->marker()+"_"+yTree->marker()+"_"+zTree->marker();
    double last = param->value();
    /*Avanseaza*/
    ++(*param);
    while(!param->isOutOfRange())
    {
        P = Point(xTree->eval(),yTree->eval(),zTree->eval());
        P.setParameterValue(param->value());
        marker2 = xTree->marker()+"_"+yTree->marker()+"_"+zTree->marker();
        /*
          daca punctul nu e finit sau nu e valid sau a fost obtinut pe o ramura diferita
          a unei functii ce descrie o ecuatie parametrica
            -intoarce-te si cauta un punct valid de pe aceeasi ramura = ultimul punct al
             segmentului curent
            -creeaza segmentul de curba urmator si calculeaza-l
         */
        if(!P.isValid() || !P.isFinite() || (marker2!=marker1))
        {
            bool found = false;
            while(!P.isValid() || !P.isFinite() || (marker2!=marker1))
            {
                *param -= step;
                if(param->value() <= last)
                    break;
                P = Point(xTree->eval(),yTree->eval(),zTree->eval());
                P.setParameterValue(param->value());
                marker2 = xTree->marker()+"_"+yTree->marker()+"_"+zTree->marker();
                if(P.isValid() && P.isFinite() && (marker2==marker1))
                    found = true;
            }
            if(found)
            {
                _points[P.parameterValue()]=P;
                if(_xMin > P.x())
                 _xMin = P.x();
                if(_yMin > P.y())
                    _yMin = P.y();
                if(_zMin > P.z())
                    _zMin = P.z();
                if(_xMax < P.x())
                    _xMax = P.x();
                if(_yMax < P.y())
                    _yMax = P.y();
                if(_zMax < P.z())
                    _zMax = P.z();
                last = P.parameterValue()+step;
            }
            else
            {
                ++(*param);
                --(*param);
                last = param->value();
            }
            Parameter *a = qobject_cast<Parameter*>(param->clone());
            a->interval().setInf(last);
            int idx = param->index();
            int count = param->valuesCount();
            a->setValuesCount(count - idx + 1);
            a->reset();
            CurveSegment3D *segment = new CurveSegment3D(_expression,this);
            segment->setColor(_color);
            QList<Parameter*>::iterator it;
            for(it=_parameters.begin();it!=_parameters.end();++it)
                segment->updateParameter(*it);
            segment->updateParameter(a);
            segment->compute();
            _next = segment;

            break;
        }
        else
        {
            _points[P.parameterValue()]=P;
            if(_xMin > P.x())
             _xMin = P.x();
            if(_yMin > P.y())
                _yMin = P.y();
            if(_zMin > P.z())
                _zMin = P.z();
            if(_xMax < P.x())
                _xMax = P.x();
            if(_yMax < P.y())
                _yMax = P.y();
            if(_zMax < P.z())
                _zMax = P.z();
           last =param->value();
            ++(*param);
        }
    }
    if(_next){
        if(_xMin > _next->xMin())
         _xMin = _next->xMin();
        if(_yMin > _next->yMin())
            _yMin = _next->yMin();
        if(_zMin > _next->zMin())
            _zMin = _next->zMin();
        if(_xMax < _next->xMax())
            _xMax = _next->xMax();
        if(_yMax < _next->yMax())
            _yMax = _next->yMax();
        if(_zMax < _next->zMax())
            _zMax = _next->zMax();
    }
    reset();
}

void CurveSegment3D::draw(){

    if(!isEmpty()){
        QMap<double,Point>::iterator p = _points.begin();
        glBegin(GL_LINE_STRIP);
        glColor3d(_color.redF(),_color.greenF(),_color.blueF());
        while(p!=_points.end()){
            glVertex3d(p.value().x(),p.value().y(),p.value().z());
            ++p;
        }
        glEnd();
    }

    if(_next)
        _next->draw();

}

Object *CurveSegment3D::clone() const{
    return new CurveSegment3D(_expression);
}

void CurveSegment3D::cleanUpPoints(){
    _points.clear();
    if(_next)
        delete _next;
    _next = 0;
}

void CurveSegment3D::cleanUp(){
    ParametricObject3D::cleanUp();
    cleanUpPoints();
}

bool CurveSegment3D::isValid() const {
    if(ParametricObject3D::isValid()){
       if(autoParameters().count()==1)
            return true;
    }
    return false;
}

bool CurveSegment3D::isEmpty() const {
    bool nextEmpty = true;
    if(_next)
        nextEmpty = _next->isEmpty();
    return (_points.count()==0) && nextEmpty;
}\

int CurveSegment3D::type() const {
    return Object::Curve;
}

const QMap<double, Point> & CurveSegment3D::points() const{
    return _points;
}

int CurveSegment3D::count() const{
    if(_next)
        return _points.count()+_next->count();
    return _points.count();
}
